package util

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"radar/nimrod/model"
	"radar/nimrod/model/header"
)

// Write out file
func Write(dat []byte, out string) {
	f, createErr := os.Create(out)
	if createErr != nil {
		fmt.Fprintf(os.Stderr, "error occured when creating file %s: %v", out, createErr)
		return
	}
	defer f.Close()
	n, writeErr := f.Write(dat)
	if writeErr != nil {
		fmt.Fprintf(os.Stderr, "error occured when writing file %s: %v", out, writeErr)
		return
	}
	fmt.Printf("wrote %d bytes\n", n)
	fmt.Printf("data: %v\n", dat)
}

// WriteNimrodFileHeader returns raw polar file
func WriteNimrodFileHeader(filePath string, outputFilePath string) {
	f, err := os.Create(outputFilePath)
	defer f.Close()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[ERROR]: %v", err)
	}

	bufWriter := bufio.NewWriter(f)
	volume, err := readNimrod(filePath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error occured: %v", err)
	}
	volumeHeader := volume.Header
	// get radar center lat, lon and the length of bin
	radarCenter := model.RadarCenter{}
	radarCenter.Lat, radarCenter.Lon = getCenterLatLon(volumeHeader)
	radarCenter.BinLen = float64(BytesToUint16(volumeHeader["ProcessorConf"]["BinLength"])[0])
	// print volume header
	for _, subHeaderName := range header.VolumeKey {
		// fmt.Printf("%s\n", subHeaderName)
		bufWriter.WriteString(fmt.Sprintf("%s\n", subHeaderName))
		for _, volumeKey := range header.SubHeader[subHeaderName] {
			var volumeItem interface{}
			if subHeaderName == "SiteInfo" {
				volumeItem = BytesToInt16(volumeHeader[subHeaderName][volumeKey])
			} else {
				volumeItem = BytesToUint16(volumeHeader[subHeaderName][volumeKey])
			}
			// fmt.Fprintf(os.Stdout, "\t%s: %v\n", volumeKey, volumeItem)
			bufWriter.WriteString(fmt.Sprintf("\t%s: %v\n", volumeKey, volumeItem))
		}
	}
	scans := volume.Scans
	// read scans
	for scanNo := int16(0); scanNo < volume.ScansNum; scanNo++ {
		scan := scans[scanNo]
		// fmt.Println("Scan Header ", scanNo)
		bufWriter.WriteString(fmt.Sprintf("Scan Header %d\n", scanNo))
		for _, scanKey := range header.ScanKey {
			scanItem := BytesToUint16(scan.Header[scanKey])
			// fmt.Fprintf(os.Stdout, "\t%s: %v\n", scanKey, scanItem)
			bufWriter.WriteString(fmt.Sprintf("\t%s: %v\n", scanKey, scanItem))
		}

		// read ray header and data
		rays := scan.Rays
		min := model.RadarGrid{Lat: 53.181954, Lon: -2.680139}
		max := model.RadarGrid{Lat: 53.990923, Lon: -1.320580}
		for rayNo := int16(0); rayNo < scan.RaysNum; rayNo++ {
			ray := rays[rayNo]
			// fmt.Println("Ray Header ", rayNo)
			bufWriter.WriteString(fmt.Sprintf("Ray Header %d\n", rayNo))

			// read ray header
			for _, rayKey := range header.RayKey {
				rayItem := BytesToUint16(ray.Header[rayKey])
				// fmt.Fprintf(os.Stdout, "\t%s: %v\n", rayKey, rayItem)
				bufWriter.WriteString(fmt.Sprintf("\t%s: %v\n", rayKey, rayItem))
			}
			// get ray azimuth angle
			grids := make([]model.RadarGrid, len(ray.Data))
			azimuth := float64(BytesToUint16(ray.Header["AzimuthCentreAngle"])[0])
			// calculate the lat and lon of every grid
			for i := 0; i < len(ray.Data); i++ {
				gridIndex := i + 1
				grids[i].Lat, grids[i].Lon, grids[i].Altitude = getGridLatLon(radarCenter, gridIndex, azimuth, 0)
			}
			ray.Grids = grids
			// filter
			extractedGrids := ray.ExtractGivenRegion(min, max)
			extractedData := ray.ExtractData(min, max)
			data := []float64{}
			for _, d := range extractedData {
				data = append(data, math.Pow(float64(d)/2000, 1/1.6))
			}
			// read data in this ray
			// fmt.Fprintf(os.Stdout, "\t%v\n", ray.Data)
			bufWriter.WriteString(fmt.Sprintf("\t%.3f\n", data))
			// write out cartesian coordiantes
			bufWriter.WriteString(fmt.Sprintf("\t%.4f\n", extractedGrids))
		}
	}
	bufWriter.Flush()
}

func getGridLatLon(center model.RadarCenter, gridIndex int, azimuth float64, elevation float64) (float64, float64, float64) {

	azimuthIncr := func(incr float64) float64 {
		radius := float64(6378100)
		radian := incr / radius
		return radian * 180 / math.Pi
	}

	lonIncr := float64(gridIndex) * center.BinLen * math.Sin(azimuth/100)
	latIncr := float64(gridIndex) * center.BinLen * math.Cos(azimuth/100)
	lon := center.Lon + azimuthIncr(lonIncr)
	lat := center.Lat + azimuthIncr(latIncr)
	altitude := float64(gridIndex) * center.BinLen * math.Tan(elevation)
	return lat, lon, altitude
}

// Write2WRFDA reads the data files in data dir, and write to the out dir
func Write2WRFDA(inDir, outDir string) error {
	fileGroups, err := GetFileGroups(inDir)
	if err != nil {
		return fmt.Errorf("[ERROR] reading file groups: %v", err)
	}

	for _, group := range fileGroups {
		err := WRFDAFormat(group, outDir)
		if err != nil {
			return err
		}
	}

	return nil
}

// WRFDAFormat format as the input of WRFDA
func WRFDAFormat(group model.FileNames, outDir string) error {
	// create output file
	dateTime := SplitFileName(group[len(group)-1], '-', '_', '.')[7]
	outFileName := filepath.Join(outDir, dateTime+".radar")
	output, err := os.Create(outFileName)
	defer output.Close()
	if err != nil {
		return err
	}
	buf := bufio.NewWriter(output)
	buf.WriteString(fmt.Sprintf("TOTAL NUMBER = %d\n", 1))
	buf.WriteString("#-----------------#\n")
	buf.WriteString("\n")
	// read the file in a group
	nimrodRadar := model.NimrodRadar{}
	for _, f := range group {
		radarCenter, nimrodVolume, err := ReadNimrodFile(f)
		if err != nil {
			return err
		}
		// get nimrodRadar volumes
		nimrodRadar.Volumes = append(nimrodRadar.Volumes, nimrodVolume)
		// check consistency of nimrodRadar center
		if nimrodRadar.Center != (model.RadarCenter{}) {
			if nimrodRadar.Center != radarCenter {
				return fmt.Errorf("[ERROR] inconsistent nimrodRadar center")
			}
			continue
		}
		nimrodRadar.Center = radarCenter
	}
	// write nimrodRadar header
	// calculate total sounding
	totalSounding := 0
	for rayIndex := 0; rayIndex < nimrodRadar.Volumes[0].Scans[0].RaysNum; rayIndex++ {
		totalSounding += nimrodRadar.Volumes[0].Scans[0].Rays[rayIndex].BinsNum
	}

	buf.WriteString(fmt.Sprintf("RADAR\t%s\t%.3f\t%.3f\t%.1f\t%s\t%d\t%d\n",
		nimrodRadar.Center.Name,
		nimrodRadar.Center.Lon, nimrodRadar.Center.Lat, nimrodRadar.Center.Altitude,
		nimrodRadar.Center.DateTime, totalSounding, len(nimrodRadar.Volumes)))
	buf.WriteString("#-------------------------------------------------------------------------------#\n")
	buf.WriteString("\n")

	for rayIndex := 0; rayIndex < nimrodRadar.Volumes[0].Scans[0].RaysNum; rayIndex++ {
		for binIndex := 0; binIndex < nimrodRadar.Volumes[0].Scans[0].Rays[rayIndex].BinsNum; binIndex++ {
			// check sounding number
			elNum := 0
			for _, v := range nimrodRadar.Volumes {
				if binIndex < len(v.Scans[0].Rays[rayIndex].Reflectivity) {
					elNum++
				}
			}

			buf.WriteString(fmt.Sprintf("FM-128	RADAR\t%s\t%.3f\t%.3f\t%.1f\t%d\n",
				nimrodRadar.Center.DateTime,
				nimrodRadar.Volumes[0].Scans[0].Rays[rayIndex].Grids[binIndex].Lat,
				nimrodRadar.Volumes[0].Scans[0].Rays[rayIndex].Grids[binIndex].Lon,
				nimrodRadar.Center.Altitude,
				elNum))
			for _, v := range nimrodRadar.Volumes {
				if binIndex < len(v.Scans[0].Rays[rayIndex].Reflectivity) {
					ref := v.Scans[0].Rays[rayIndex].Reflectivity[binIndex]
					buf.WriteString(fmt.Sprintf("\t%.1f\t%.3f\t%.1f\t%.3f\t%.3f\t%.1f\t%.3f\n",
						v.Scans[0].Rays[rayIndex].Grids[binIndex].Altitude,
						0.0, 0.0, 1.0, float64(ref)/100, 0.0, 1.0))
				}
			}
		}
	}

	buf.Flush()
	return nil
}
